
#include <cmath>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <random>
#include <sstream>
#include <vector>

#include "SDL.h"
#include "SDL_mixer.h"
#include "ddc.h"

#include "contra_anim.h"
#include "contra_resource.h"
#include "contra_utils.h"

static std::default_random_engine _randomEngine;

static std::vector<void *> _IntroSceneMemPool;

using std::pow, std::sqrt;

class _Contra_SharedData {
public:
    static int score;
    static int life;
};

int _Contra_SharedData::score = 0;
int _Contra_SharedData::life = 0;

// Intro Scene {

class Intro: public DDC_Behavior {
    char state = 0;
    SDL_Texture *texture;

    int beginTick;
    int alpha = 255;

public:
    virtual void OnStart() {
        DDC_Sprite2D *s2d =
            _attachedGameObject->
                GetComponentByID<DDC_Sprite2D>(DDC_COMPONENT_SPRITE2D);

        texture = s2d->GetSpriteRender<DDC_StaticSpriteRenderer>()
                    ->spriteTexture->texture->baseTexture;
        beginTick = DDC_Time::GetTotalTicks() + 2000;
    }

    virtual void Update() {
        int t;
        if (alpha > 0 &&
                (t = DDC_Time::GetTotalTicks() - beginTick) > 0) {

            alpha = -(255 / 0.8) * ((t / 1000.0) - 0.8);
            if (alpha < 0)
                alpha = 0;
            SDL_SetTextureAlphaMod(texture, alpha);
            return;
        } else if (alpha == 0)
            Dodoco::GetDodoco()->SwitchScene(1);
    }
};

DDC_Scene *Contra_IntroSceneCreate() {
    DDC_BaseSDLTexture *baseIntroTexture =
        DDC_BaseSDLTexture::LoadFromFile("res/texture/intro.png");

    if (!baseIntroTexture) {
        std::cerr << "Contra_IntroSceneCreate(): load intro.png fault: "
                  << SDL_GetError() <<std::endl;
        return NULL;
    }

    _IntroSceneMemPool.push_back((void*)baseIntroTexture);

    DDC_Rect rect = DDC_Rect {0, 0, 800, 600};

    DDC_SpriteTexture *st = DDC_SpriteTexture::CutFromBaseTexture(baseIntroTexture,
                                                                  rect);
    _IntroSceneMemPool.push_back((void*)st);

    SDL_SetTextureBlendMode(baseIntroTexture->baseTexture,
                            SDL_BLENDMODE_BLEND);

    DDC_Scene *scene = Dodoco::GetDodoco()->NewScene("Intro");
    DDC_GameObject *go = scene->NewGameObject("intro")->SetGameObjectRect(DDC_RectToFRect(&rect));
    DDC_StaticSpriteRenderer *ssr =
        go->AddCompoment<DDC_Sprite2D>()->UseSpriteRenderer<DDC_StaticSpriteRenderer>();
    go->AddBehavior<Intro>();

    ssr->SetSpriteTexture(st);

    return scene;
}

void Contra_IntroSceneDestory() {
    for (void *elem: _IntroSceneMemPool)
        delete elem;
}

// } Intro Scene

// Menu Scene {

static std::vector<void *> _MenuSceneMemPool;

class Menu: public DDC_Behavior {
    char _state = 0;
    float v = -200;

    void _Move() {
        _attachedGameObject->gameObjectRect.x += v * DDC_Time::GetTimeDeltaSecond();
    }
public:
    virtual void Update() {
        if (_state == 0) {
            _Move();
            if (_attachedGameObject->gameObjectRect.x <= 0) {
                _attachedGameObject->gameObjectRect.x = 0;
                _state = 1;
            }
        } else if (_state == 1) {
            Mix_Chunk *chunk = Contra_gMusicChucks[CONTRA_RES_AUDIO_INTRO];
            Mix_PlayChannel(-1, chunk, 0);
            _state = 2;
        }
        if (DDC_InputManager::IsKeyDown() && DDC_InputManager::GetKeyState(DDC_Scancode::DDC_SC_SPACE)) {
            Dodoco::GetDodoco()->SwitchScene(2);
        }
    }
};

DDC_Scene *Contra_MenuSceneCreate() {
    DDC_BaseSDLTexture *baseIntroTexture =
        DDC_BaseSDLTexture::LoadFromFile("res/texture/menu.png");

    if (!baseIntroTexture) {
        std::cerr << "Contra_MenuSceneCreate(): load menu.png fault: "
                  << SDL_GetError() <<std::endl;
        return NULL;
    }

    _IntroSceneMemPool.push_back((void*)baseIntroTexture);

    DDC_Rect rect = DDC_Rect {0, 0, 800, 600};

    DDC_SpriteTexture *st = DDC_SpriteTexture::CutFromBaseTexture(baseIntroTexture,
                                                                  rect);
    _IntroSceneMemPool.push_back((void*)st);

    DDC_Scene *scene = Dodoco::GetDodoco()->NewScene("Menu");
    DDC_GameObject *go = scene->NewGameObject("menu")->SetGameObjectRect(
                                                                         DDC_FRect{800,
                                                                                   0,
                                                                                   800,
                                                                                   600});
    DDC_StaticSpriteRenderer *ssr =
        go->AddCompoment<DDC_Sprite2D>()->UseSpriteRenderer<DDC_StaticSpriteRenderer>();
    go->AddBehavior<Menu>();

    ssr->SetSpriteTexture(st);

    return scene;
}


void Contra_MenuSceneCreateSceneDestory() {
    for (void *elem: _MenuSceneMemPool)
        delete elem;
}

// } Menu Scene

// Main Game Scene {

bool _Contra_CameraCheckOutSide(DDC_GameObject *go, int xOffest = 20, int yOffset = 20) {
    DDC_GameObject *camera = go->GetAttachedScene()->GetWorldCamera();

    int x = go->gameObjectRect.x, y = go->gameObjectRect.y;

    if (x < camera->gameObjectRect.x - xOffest || x > camera->gameObjectRect.x + 320 + xOffest ||
        y < -yOffset || y > camera->gameObjectRect.y + 240 + yOffset) {

        return true;
    }
    return false;
}

bool _Contra_MoveFree(DDC_GameObject *go) {
    go->gameObjectRect.x = -100;
    go->gameObjectRect.y = -100;
}

static int _score = 0;

static const int _CONTRA_ANIM_PLAY_INTERVAL = 135;

static int _CONTRA_BILL_STATE_WALKR;
static int _CONTRA_BILL_STATE_WALKS;
static int _CONTRA_BILL_STATE_WALKSUR;
static int _CONTRA_BILL_STATE_WALKSDR;
static int _CONTRA_BILL_STATE_STAND;
static int _CONTRA_BILL_STATE_FALLFLAT;
static int _CONTRA_BILL_STATE_STANDUR;
static int _CONTRA_BILL_STATE_STANDDR;
static int _CONTRA_BILL_STATE_STANDUP;
static int _CONTRA_BILL_STATE_ROLL;
static int _CONTRA_BILL_STATE_DIE;
static int _CONTRA_ENEMY1_STATE_RUN;
static int _CONTRA_ENEMY2_STATE_SHOTR;
static int _CONTRA_ENEMY2_STATE_SHOTD;
static int _CONTRA_ENEMY2_STATE_SHOTU;

static std::vector<void *> _LevelMemPool;

static std::vector<DDC_Rect> _LevelColliders = {
    DDC_Rect {32, 116, 1405, 7},
    DDC_Rect {1376, 83, 512, 5},
    DDC_Rect {1859, 115, 217, 6},
    DDC_Rect {2048, 83, 160, 6},
    DDC_Rect {2240, 115, 64, 5},
    DDC_Rect {2176, 146, 96, 6},
    DDC_Rect {2336, 148, 64, 5},
    DDC_Rect {2368, 179, 96, 5},
    DDC_Rect {2496, 211, 32, 5},
    DDC_Rect {2528, 163, 32, 5},
    DDC_Rect {2464, 115, 64, 5},
    DDC_Rect {2496, 83 , 64, 5},
    DDC_Rect {2592, 115, 64, 5},
    DDC_Rect {2624, 148, 160, 5},
    DDC_Rect {2720, 211, 32, 5},
    DDC_Rect {2720, 211, 95, 5},
    DDC_Rect {2496, 211, 96, 5},
    DDC_Rect {2848, 180, 64, 5},
    DDC_Rect {2944, 147, 64, 5},
    DDC_Rect {3008, 115, 128, 5},
    DDC_Rect {3136, 147, 32, 5},
    DDC_Rect {3040, 163, 96, 5},
    DDC_Rect {3168, 180, 32, 5},
    DDC_Rect {3008, 211, 240, 5},
    DDC_Rect {1408, 211, 96, 5},
    DDC_Rect {1504, 164, 64, 5},
    DDC_Rect {1600, 148, 224, 5},
    DDC_Rect {1728, 211, 192, 5},
    DDC_Rect {1920, 180, 64, 5},
    DDC_Rect {2016, 180, 64, 5},
    DDC_Rect {2112, 164, 32, 5},
    DDC_Rect {2336, 211, 32, 5},
};

/* Spawn meta: (leftX, rightX, spawnSide)
 * spawnSide:
 * 0: left
 * 1: right
 * 2: both side
 */
static std::vector<int> _LevelEnemy1SpawnMeta = {
    154, 1188, 2,
    1451, 1786, 1,
    2222, 3000, 2,
    3000, 3250, 0,
};

static std::vector<DDC_Vector2> _Enemy2SpawnPoints = {
    DDC_Vector2 {296, 172},
    DDC_Vector2 {614, 172},
    DDC_Vector2 {1478, 172},
    DDC_Vector2 {1828, 172},
    DDC_Vector2 {1413, 76},
    DDC_Vector2 {1893, 172},
    DDC_Vector2 {2118, 124},
    DDC_Vector2 {2342, 172},
    DDC_Vector2 {2723, 172},
    DDC_Vector2 {3013, 172},
};

static DDC_Rect _DeathCollider = {
    0, 235, 3328, 5
};

enum Contra_MobDirectionHoriz {
    CONTRA_DIR_LEFT = 4,
    CONTRA_DIR_RIGHT = 8,
};

enum Contra_MobDirectionVert {
    CONTRA_DIR_UP = 1,
    CONTRA_DIR_DOWN = 2,
    CONTRA_DIR_VERT_NONE = 4,
};

enum Contra_MobState {
    CONTRA_BILL_STAND,
    CONTRA_BILL_FALL_FLAT,
    CONTRA_BILL_RUN,
};

enum Contra_BulletType {
    CONTRA_BULLET_BILL,
    CONTRA_BULLET_ENEMY,
    CONTRA_BULLET_BOSS,
};

class Contra_BulletController: public DDC_Behavior {
    float colliderWidth, colliderHeight;
    DDC_Vector2 pos;

protected:
    DDC_Vector2 v;

public:
    Contra_BulletController *SetCollider(int w, int h) {
        colliderWidth = w;
        colliderHeight = h;
        return this;
    }

    Contra_BulletController *SetPos(DDC_Vector2 p) {
        _attachedGameObject->gameObjectRect.x = p.x;
        _attachedGameObject->gameObjectRect.y = p.y;
        return this;
    }

    Contra_BulletController *SetV(DDC_Vector2 v) {
        this->v = v;
        return this;
    }

    virtual void Update() {
        if (_Contra_CameraCheckOutSide(_attachedGameObject)) {
            return;
        }

        _attachedGameObject->gameObjectRect.x += v.x * DDC_Time::GetTimeDeltaSecond();
        _attachedGameObject->gameObjectRect.y += v.y * DDC_Time::GetTimeDeltaSecond();
    }
};

class Contra_BossBulletController: public Contra_BulletController {
    float g = 800;
public:
    virtual void Update() {
        if (_Contra_CameraCheckOutSide(_attachedGameObject)) {
            return;
        }

        v.y += g * DDC_Time::GetTimeDeltaSecond();

        _attachedGameObject->gameObjectRect.x += v.x * DDC_Time::GetTimeDeltaSecond();
        _attachedGameObject->gameObjectRect.y += v.y * DDC_Time::GetTimeDeltaSecond();
    }
};

class Contra_BulletManager {
    static std::vector<DDC_GameObject *> _BillBulletPool;
    static std::vector<DDC_GameObject *> _EnemyBulletPool;
    static std::vector<DDC_GameObject *> _BossBulletPool;

    static DDC_GameObject *_GetFreeBullet(std::vector<DDC_GameObject *> &where) {
        for (DDC_GameObject *bullet: where) {
            if (_Contra_CameraCheckOutSide(bullet)) {
                return bullet;
            }
        }
        return nullptr;
    }

public:
    static std::vector<DDC_GameObject *> &GetBillBulletPool() {
        return _BillBulletPool;
    }

    static std::vector<DDC_GameObject *> &GetEnemyBulletPool() {
        return _EnemyBulletPool;
    }

    static std::vector<DDC_GameObject *> &GetBossBulletPool() {
        return _BossBulletPool;
    }

    static void InitBillBulletPool(DDC_SpriteTexture *bulletTexture, DDC_Scene *scene) {
        if (_BillBulletPool.size() > 0)
            return;

        Dodoco *dodoco = Dodoco::GetDodoco();
        DDC_GameObject *bullet;

        for (int i = 0; i < 20; ++i) {
            bullet = scene->NewGameObject("bullet");
            bullet->AddBehavior<Contra_BulletController>();
            bullet->AddCompoment<DDC_Sprite2D>()
                ->UseSpriteRenderer<DDC_StaticSpriteRenderer>()
                ->SetSpriteTexture(bulletTexture);
            _BillBulletPool.push_back(bullet);
            bullet->gameObjectRect.height = 5;
            bullet->gameObjectRect.width = 5;
            bullet->gameObjectRect.x = -100;
        }
    }

    static void InitEnemyBulletPool(DDC_SpriteTexture *bulletTexture, DDC_Scene *scene) {
        if (_EnemyBulletPool.size() > 0)
            return;

        Dodoco *dodoco = Dodoco::GetDodoco();
        DDC_GameObject *bullet;

        for (int i = 0; i < 50; ++i) {
            bullet = scene->NewGameObject("EnemyBullet");
            bullet->AddBehavior<Contra_BulletController>();
            bullet->AddCompoment<DDC_Sprite2D>()
                ->UseSpriteRenderer<DDC_StaticSpriteRenderer>()
                ->SetSpriteTexture(bulletTexture);
            _EnemyBulletPool.push_back(bullet);
            bullet->gameObjectRect.height = 3;
            bullet->gameObjectRect.width = 3;
            bullet->gameObjectRect.x = -100;
            bullet->gameObjectRect.y = -100;
        }
    }

    static void InitBossBulletPool(DDC_SpriteTexture *bulletTexture, DDC_Scene *scene) {
        if (_BossBulletPool.size() > 0)
            return;

        Dodoco *dodoco = Dodoco::GetDodoco();
        DDC_GameObject *bullet;

        for (int i = 0; i < 10; ++i) {
            bullet = scene->NewGameObject("BossBullet");
            bullet->AddBehavior<Contra_BossBulletController>();
            bullet->AddCompoment<DDC_Sprite2D>()
                ->UseSpriteRenderer<DDC_StaticSpriteRenderer>()
                ->SetSpriteTexture(bulletTexture);
            _BossBulletPool.push_back(bullet);
            bullet->gameObjectRect.height = 8;
            bullet->gameObjectRect.width = 8;
            bullet->gameObjectRect.x = -100;
            bullet->gameObjectRect.y = -100;
        }
    }

    static void ShotBullet(int x, int y, DDC_Vector2 v, int bulletType) {
        DDC_GameObject *bullet = nullptr;
        if (bulletType == Contra_BulletType::CONTRA_BULLET_BILL) {
            bullet = _GetFreeBullet(_BillBulletPool);
        } else if (bulletType == Contra_BulletType::CONTRA_BULLET_ENEMY) {
            bullet = _GetFreeBullet(_EnemyBulletPool);
        } else if (bulletType == Contra_BulletType::CONTRA_BULLET_BOSS) {
            bullet = _GetFreeBullet(_BossBulletPool);
        }

        if (bullet == nullptr) {
            std::cerr << "[Contra]: Warning: ShotBullet(): bullet == nullptr\n";
            return;
        }
        Contra_BulletController *controller =
            bullet->GetBehavior<Contra_BulletController>();
        controller->SetPos(DDC_Vector2{x, y})->SetV(v);
    }

    static DDC_GameObject *HitBullet(DDC_FRect rect) {
        for (DDC_GameObject *bullet: _BillBulletPool) {
            if (!_Contra_CameraCheckOutSide(bullet) &&
                DDC_Math::RectIntersect(rect, bullet->gameObjectRect))

                return bullet;
        }
        return nullptr;
    }
};

std::vector<DDC_GameObject*> Contra_BulletManager::_BillBulletPool = {};
std::vector<DDC_GameObject*> Contra_BulletManager::_EnemyBulletPool = {};
std::vector<DDC_GameObject*> Contra_BulletManager::_BossBulletPool = {};

class Enemy1Controller: public DDC_Behavior {
    const float g = 60;
    float vy = 0;
    float vx = 70;

    int _directionHorz = Contra_MobDirectionHoriz::CONTRA_DIR_RIGHT;
    int _directionVert = Contra_MobDirectionVert::CONTRA_DIR_VERT_NONE;

    Contra_MobState _state = Contra_MobState::CONTRA_BILL_STAND;

    int _fixedHeight = 40;

    bool _jumping = false;
    bool _inDeath = false;
    bool _shoting = false;

    int _shotStartTicks = -600;

    int _feetColliderHeight = 3;
    int _feetColliderWidth = 19;

    int  _fallFlatColliderWidth = 32;
    int  _fallFlatColliderHeight = 14;

    int _bodyStandColliderHeight = 30;
    int _bodyStandColliderWidth = 23;

    int _lastActionTick = -3000;

    DDC_Sprite2D *_sprite2d;
    DDC_GameObject *_player;

    void SetAnimateState() {
        if (_jumping) {
                return;
        }
    }

    int CheckTouchGround() {
        int x = _attachedGameObject->gameObjectRect.x;
        int y = _attachedGameObject->gameObjectRect.y;
        int h = _attachedGameObject->gameObjectRect.height;
        int w = _attachedGameObject->gameObjectRect.width;

        DDC_Rect feetCollider = {x, y + h - _feetColliderHeight,
                                 _feetColliderWidth, _feetColliderHeight};

        if (DDC_Math::RectIntersect(feetCollider, _DeathCollider))
            return -2;

//        for (DDC_Rect groundCollider: _LevelColliders) {
//            if (DDC_Math::RectIntersect(feetCollider, groundCollider))
//        }

//        SDL_SetRenderDrawColor(__gDodocoMainRenderer, 255, 0, 0, 255);
//        SDL_RenderDrawRect(__gDodocoMainRenderer, (SDL_Rect*)&feetCollider);
        for (DDC_Rect levelCollider: _LevelColliders) {
//            SDL_RenderDrawRect(__gDodocoMainRenderer, (SDL_Rect*)&levelCollider);
            if (DDC_Math::RectIntersect(feetCollider, levelCollider))
                return y;
        }
//        SDL_SetRenderDrawColor(__gDodocoMainRenderer, 0, 0, 0, 0);
        return -1;
    }

    void _CheckAndAction() {
        int playerX = _player->gameObjectRect.x;

        DDC_FRect borderRect = {_attachedGameObject->gameObjectRect.x - 20, _attachedGameObject->gameObjectRect.y,
                                _attachedGameObject->gameObjectRect.width + 40,
                                _attachedGameObject->gameObjectRect.height};

        if (Contra_BulletManager::HitBullet(borderRect) &&
            !_Contra_CameraCheckOutSide(_attachedGameObject, 20, 40)) {

            _jumping = true;
            vy = -200;
            _attachedGameObject->gameObjectRect.y -= 5;

            _lastActionTick = DDC_Time::GetTotalTicks();
        }

//        if (abs(playerX - _attachedGameObject->gameObjectRect.x) < 50 && !_jumping) {
//            _jumping = true;
//            vy = -250;
//            _attachedGameObject->gameObjectRect.y -= 5;
//        }
    }

    void _Die() {
        _attachedGameObject->gameObjectRect.x = -60;
    }

public:
    void WalkTowardPlayer() {
        int side = _player->gameObjectRect.x - _attachedGameObject->gameObjectRect.x;
        if (side < 0) {
            vx = -70;
            _sprite2d->SetSpriteFlipMode(DDC_SpriteFlip::FLIP_HORIZONTAL);
        } else {
            vx = 70;
            _sprite2d->SetSpriteFlipMode(DDC_SpriteFlip::FLIP_NONE);
        }
    }

    virtual void OnStart() {
        _player = _attachedGameObject->GetAttachedScene()->GetGameObejct("bill");
        if (_player == nullptr) {
            std::cerr << "[Contra] Warning: enemy1 cannot get the pointer of Bill\n";
        }

        _sprite2d = _attachedGameObject
                        ->GetComponentByID<DDC_Sprite2D>(DDC_COMPONENT_SPRITE2D);
        DDC_AnimateSpriteRenderer::SetState(_attachedGameObject, _CONTRA_ENEMY1_STATE_RUN);
        DDC_AnimateSpriteRenderer::ReplayGameObject(_attachedGameObject);

        _lastActionTick = DDC_Time::GetTotalTicks();
    }

    virtual void Update() {
        if (_Contra_CameraCheckOutSide(_attachedGameObject, 80, 40))
            return;

        float &playerX = _attachedGameObject->gameObjectRect.x;
        float &playerY = _attachedGameObject->gameObjectRect.y;

        int groundY = CheckTouchGround();

        if (groundY == -2) {
            _Die();
        }

        if (groundY > 0 && vy >= 0) {
            playerY = groundY;
            vy = 0;
            _jumping = false;
        } else {
            playerY += vy * DDC_Time::GetTimeDeltaSecond();
            vy += g * DDC_Time::GetTimeDeltaSecond() * 10;
        }

        playerX += vx * DDC_Time::GetTimeDeltaSecond();

        _state = Contra_MobState::CONTRA_BILL_STAND;
        _directionVert = Contra_MobDirectionVert::CONTRA_DIR_VERT_NONE;

        /* check and do action per 1 second. */
        if (DDC_Time::GetTotalTicks() - _lastActionTick >= 3000) {
            _CheckAndAction();
        }

        SetAnimateState();
    }
};

class Enemy2Controller: public DDC_Behavior {
    DDC_GameObject *_player;
    DDC_Sprite2D *_sprite2d;

    int _lastCheckTicks = 0;
    int _lastShotTicks = 0;

    int _shotbulletCount = 1;

    int _directionHorz = Contra_MobDirectionHoriz::CONTRA_DIR_RIGHT;
    int _directionVert = Contra_MobDirectionVert::CONTRA_DIR_VERT_NONE;

    void _SetAnimateState() {
        if (_directionVert == Contra_MobDirectionVert::CONTRA_DIR_UP) {
            DDC_AnimateSpriteRenderer::SetState(_attachedGameObject,
                                                _CONTRA_ENEMY2_STATE_SHOTU);
        } else {
            DDC_AnimateSpriteRenderer::SetState(_attachedGameObject,
                                                _CONTRA_ENEMY2_STATE_SHOTR);
        }
    }

    void _GetShotBulletInfo(int *x, int *y, DDC_Vector2 *v) {
        if (_directionVert == Contra_MobDirectionVert::CONTRA_DIR_UP) {
            *x = _attachedGameObject->gameObjectRect.x +
                                            (_directionHorz == Contra_MobDirectionHoriz::CONTRA_DIR_LEFT ?
                                             -2 : 20);
            *y = _attachedGameObject->gameObjectRect.y;
            v->x = (_directionHorz == Contra_MobDirectionHoriz::CONTRA_DIR_LEFT ?
                    -141 : 141);
            v->y = -141;
        } else {
            *x = _attachedGameObject->gameObjectRect.x +
                                            (_directionHorz == Contra_MobDirectionHoriz::CONTRA_DIR_LEFT ?
                                             -2 : 24);
            *y = _attachedGameObject->gameObjectRect.y + 12;
            v->x = (_directionHorz == Contra_MobDirectionHoriz::CONTRA_DIR_LEFT ?
                    -200 : 200);
        }
    }

    void _CheckAndAction() {
        float playerDistance = DDC_Math::RectDistance(_player->gameObjectRect,
                                                      _attachedGameObject->gameObjectRect);

        if (playerDistance > 250)
            return;

        /* Looking at the player */
        float cosVal = (_player->gameObjectRect.x - _attachedGameObject->gameObjectRect.x)
                        / playerDistance;

        if (cosVal < 0) {
            _directionHorz = Contra_MobDirectionHoriz::CONTRA_DIR_LEFT;
            _sprite2d->SetSpriteFlipMode(DDC_SpriteFlip::FLIP_HORIZONTAL);
        } else {
            _directionHorz = Contra_MobDirectionHoriz::CONTRA_DIR_RIGHT;
            _sprite2d->SetSpriteFlipMode(DDC_SpriteFlip::FLIP_NONE);
        }

        cosVal = cosVal > 0 ? cosVal : -cosVal;

        if (cosVal < 0.707) {
            _directionVert = Contra_MobDirectionVert::CONTRA_DIR_UP;
        } else {
            _directionVert = Contra_MobDirectionVert::CONTRA_DIR_VERT_NONE;
        }

        _SetAnimateState();

        if (playerDistance < 150) {
            int bulletX, bulletY;
            DDC_Vector2 bulletV;

            _shotbulletCount++;

            _GetShotBulletInfo(&bulletX, &bulletY, &bulletV);
            Contra_BulletManager::ShotBullet(bulletX, bulletY, bulletV,
                                             Contra_BulletType::CONTRA_BULLET_ENEMY);
        }
    }

public:
    virtual void OnStart() {
        _player = _attachedGameObject->GetAttachedScene()->GetGameObejct("bill");
        if (_player == nullptr)
            std::cerr << "[Contra] Warning: enemy manager cannot get player pointer\n";

        _sprite2d = _attachedGameObject->GetComponentByID<DDC_Sprite2D>(DDC_COMPONENT_SPRITE2D);
    }

    virtual void Update() {
        if (DDC_Time::GetTotalTicks() - _lastCheckTicks >=
            ((_shotbulletCount % 10 == 0 ? 250 : 0) + 150)) {

            _lastCheckTicks = DDC_Time::GetTotalTicks();
            _CheckAndAction();
        }
    }
};

class BossController: public DDC_Behavior {
    DDC_GameObject *_player;

    int _lastLeftFireTicks = 0;
    int _lastRightFireTicks = -1000;

    int _lastCheckTicks = 0;

    int _hp = 100;

    bool _inDeath = false;
    int _lastDeathStartTicks = 0;

    void _Fire(int gunX, int gunY) {
        int playerX = _player->gameObjectRect.x;
        int playerY = _player->gameObjectRect.y;

        float distance = sqrt(pow(playerX - gunX, 2) + pow(playerY - gunY, 2));

        float sinVal = (gunY - playerY) / distance;
        float cosVal = (playerX - gunX) / distance;

        DDC_Vector2 v = {180, 0};

        v.x = cosVal * v.x + -sinVal * v.y;
        v.y = sinVal * v.x + cosVal * v.y;

        Contra_BulletManager::ShotBullet(gunX, gunY, v, Contra_BulletType::CONTRA_BULLET_BOSS);
    }

    void _CheckBulletHit() {
        for (DDC_GameObject *bullet: Contra_BulletManager::GetBillBulletPool()) {
            if (DDC_Math::RectIntersect(_attachedGameObject->gameObjectRect,
                                        bullet->gameObjectRect)) {

                _Contra_MoveFree(bullet);
                _hp -= 3;
                if (_hp <= 0) {
                    Mix_PlayChannel(-1, Contra_gMusicChucks[CONTRA_RES_AUDIO_BOSSDIE], 0);
                    _inDeath = true;
                    _lastDeathStartTicks = DDC_Time::GetTotalTicks();
                    _hp = 0;
                }

                _score += 50;
            }
        }
    }

public:
    virtual void OnStart() {
        _player = _attachedGameObject->GetAttachedScene()->GetGameObejct("bill");
        if (_player == nullptr)
            std::cerr << "[Contra] Warning: enemy manager cannot get player pointer\n";
    }

    virtual void Update() {
        if (_inDeath) {
            if (DDC_Time::GetTotalTicks() - _lastDeathStartTicks >= 4000) {
                _Contra_SharedData::score = _score;

                Dodoco::GetDodoco()->SwitchScene(
                            Dodoco::GetDodoco()->GetSceneIndex("WinScene"));
            } else {
                return;
            }
        }

        if (DDC_Time::GetTotalTicks() - _lastLeftFireTicks >= _randomEngine() % 500 + 750) {
            _lastLeftFireTicks = DDC_Time::GetTotalTicks();
            _Fire(3217, 128);
        }

        if (DDC_Time::GetTotalTicks() - _lastRightFireTicks >= _randomEngine() % 500 + 750) {
            _lastRightFireTicks = DDC_Time::GetTotalTicks();
            _Fire(3240, 128);
        }

        if (DDC_Time::GetTotalTicks() - _lastCheckTicks >= 45) {
            _CheckBulletHit();
        }

        if (_player->gameObjectRect.x >= 2800) {
            std::stringstream ssTitle;
            ssTitle << "GUN WALL";
            Contra_RenderText(100, 1, 120, 7, ssTitle.str());

            char hpBar[102];
            memset(hpBar + _hp + 1, '_', 100 - _hp);
            memset(hpBar + 1, '=', _hp);
            hpBar[0] = '[';
            hpBar[101] = ']';

            Contra_RenderText(5, 8, 310, 7, hpBar);
        }
    }
};

class BillController: public DDC_Behavior {
    const float g = 60;
    float vy = 0;
    float vx = 70;

    int _directionHorz = Contra_MobDirectionHoriz::CONTRA_DIR_RIGHT;
    int _directionVert = Contra_MobDirectionVert::CONTRA_DIR_VERT_NONE;

    Contra_MobState _state = Contra_MobState::CONTRA_BILL_STAND;

    int _fixedHeight = 40;

    bool _jumping = false;
    bool _inDeath = false;
    bool _shoting = false;
    bool _stop = false;

    int _shotStartTicks = -600;

    int _feetColliderHeight = 3;
    int _feetColliderWidth = 19;

    int  _fallFlatColliderWidth = 32;
    int  _fallFlatColliderHeight = 14;

    int _bodyStandColliderHeight = 30;
    int _bodyStandColliderWidth = 23;

    int _deathStartTick;
    int _lastDeathCheckTicks = -33;
    int _superTimeStartTicks = -3000;
    int _lastShotTicks = -1000;

    DDC_Sprite2D *_sprite2d;
    DDC_GameObject *_worldCamera;

    bool _CheckHit();

    void SetAnimateState() {
        if (_jumping) {
                return;
        }
        if (_state == Contra_MobState::CONTRA_BILL_RUN) {
            if (_directionVert == Contra_MobDirectionVert::CONTRA_DIR_UP) {
                DDC_AnimateSpriteRenderer::SetState(_attachedGameObject,
                                                    _CONTRA_BILL_STATE_WALKSUR);
            } else if (_directionVert == Contra_MobDirectionVert::CONTRA_DIR_DOWN) {
                DDC_AnimateSpriteRenderer::SetState(_attachedGameObject,
                                                    _CONTRA_BILL_STATE_WALKSDR);
            } else {
                if (_shoting) {
                    DDC_AnimateSpriteRenderer::SetState(_attachedGameObject,
                                                        _CONTRA_BILL_STATE_WALKS);
                } else {
                    if (DDC_Time::GetTotalTicks() - _shotStartTicks < 200) {
                        DDC_AnimateSpriteRenderer::SetState(_attachedGameObject,
                                                        _CONTRA_BILL_STATE_WALKS);
                    } else {
                        DDC_AnimateSpriteRenderer::SetState(_attachedGameObject,
                                                            _CONTRA_BILL_STATE_WALKR);
                    }
                }
            }
        } else if (_state == Contra_MobState::CONTRA_BILL_STAND) {
            if (_directionVert == Contra_MobDirectionVert::CONTRA_DIR_UP) {
                DDC_AnimateSpriteRenderer::SetState(_attachedGameObject,
                                                    _CONTRA_BILL_STATE_STANDUP);
            } else {
                DDC_AnimateSpriteRenderer::SetState(_attachedGameObject,
                                                    _CONTRA_BILL_STATE_STAND);
            }
        } else if (_state == Contra_MobState::CONTRA_BILL_FALL_FLAT) {
            DDC_AnimateSpriteRenderer::SetState(_attachedGameObject,
                                                _CONTRA_BILL_STATE_FALLFLAT);
        }
    }

    int CheckTouchGround() {
        int x = _attachedGameObject->gameObjectRect.x;
        int y = _attachedGameObject->gameObjectRect.y;
        int h = _attachedGameObject->gameObjectRect.height;
        int w = _attachedGameObject->gameObjectRect.width;

        DDC_Rect feetCollider = {x, y + h - _feetColliderHeight,
                                 _feetColliderWidth, _feetColliderHeight};

        if (DDC_Math::RectIntersect(feetCollider, _DeathCollider))
            return -2;

//        for (DDC_Rect groundCollider: _LevelColliders) {
//            if (DDC_Math::RectIntersect(feetCollider, groundCollider))
//        }

//        SDL_SetRenderDrawColor(__gDodocoMainRenderer, 255, 0, 0, 255);
//        SDL_RenderDrawRect(__gDodocoMainRenderer, (SDL_Rect*)&feetCollider);
        for (DDC_Rect levelCollider: _LevelColliders) {
//            SDL_RenderDrawRect(__gDodocoMainRenderer, (SDL_Rect*)&levelCollider);
            if (DDC_Math::RectIntersect(feetCollider, levelCollider))
                return y;
        }
//        SDL_SetRenderDrawColor(__gDodocoMainRenderer, 0, 0, 0, 0);
        return -1;
    }

    void _Respawn() {
        DDC_AnimateSpriteRenderer::SetState(_attachedGameObject, _CONTRA_BILL_STATE_STAND);
        _attachedGameObject->gameObjectRect.y = 30;
        vy = 0;
    }

    void _PerformDeath() {
        if (life == 0) {
            if (DDC_Time::GetTotalTicks() - _deathStartTick >= 2000) {
                _Contra_SharedData::score = _score;

                Dodoco::GetDodoco()->SwitchScene(
                            Dodoco::GetDodoco()->GetSceneIndex("FailScene"));
            }
        } else if (DDC_Time::GetTotalTicks() - _deathStartTick >= 1000) {
            _inDeath = false;
            _Respawn();
        }
    }

    void _CalcBulletStartState(int *x, int *y, DDC_Vector2 *v) {
        if (_directionVert == Contra_MobDirectionVert::CONTRA_DIR_UP &&
            _state == Contra_MobState::CONTRA_BILL_STAND) {

            *x = _attachedGameObject->gameObjectRect.x + 9;
            *y = _attachedGameObject->gameObjectRect.y - 5;
            v->x = 0;
            v->y = -800;

            return;
        }

        if (_state == Contra_MobState::CONTRA_BILL_FALL_FLAT) {
            if (_directionHorz == Contra_MobDirectionHoriz::CONTRA_DIR_RIGHT) {
                *x = _attachedGameObject->gameObjectRect.x + 32;
                v->x = 300;
            } else {
                *x = _attachedGameObject->gameObjectRect.x - 5;
                v->x = -300;
            }

            *y = _attachedGameObject->gameObjectRect.y + 29;
            v->y = 0;

            return;
        }

        if (_directionHorz == Contra_MobDirectionHoriz::CONTRA_DIR_RIGHT) {
            if (_directionVert == Contra_MobDirectionVert::CONTRA_DIR_VERT_NONE) {
                *x = _attachedGameObject->gameObjectRect.x + 24;
                *y = _attachedGameObject->gameObjectRect.y + 18;
                v->x = 300;
                v->y = 0;
            }

            if (_directionVert == Contra_MobDirectionVert::CONTRA_DIR_UP) {
                *x = _attachedGameObject->gameObjectRect.x + 16;
                *y = _attachedGameObject->gameObjectRect.y + 5;
                v->x = 212;
                v->y = -212;
            }

            if (_directionVert == Contra_MobDirectionVert::CONTRA_DIR_DOWN) {
                *x = _attachedGameObject->gameObjectRect.x + 20;
                *y = _attachedGameObject->gameObjectRect.y + 26;
                v->x = 212;
                v->y = 212;
            }
        }

        if (_directionHorz == Contra_MobDirectionHoriz::CONTRA_DIR_LEFT) {
            if (_directionVert == Contra_MobDirectionVert::CONTRA_DIR_VERT_NONE) {
                *x = _attachedGameObject->gameObjectRect.x;
                *y = _attachedGameObject->gameObjectRect.y + 18;
                v->x = -300;
                v->y = 0;
            }

            if (_directionVert == Contra_MobDirectionVert::CONTRA_DIR_UP) {
                *x = _attachedGameObject->gameObjectRect.x;
                *y = _attachedGameObject->gameObjectRect.y + 5;
                v->x = -212;
                v->y = -212;
            }

            if (_directionVert == Contra_MobDirectionVert::CONTRA_DIR_DOWN) {
                *x = _attachedGameObject->gameObjectRect.x;
                *y = _attachedGameObject->gameObjectRect.y + 26;
                v->x = -212;
                v->y = 212;
            }
        }
    }

    void _HandleGodKey() {
        if (DDC_InputManager::GetKeyState(DDC_Scancode::DDC_SC_1)) {

            _attachedGameObject->gameObjectRect.x -= 5;
        }

        if (DDC_InputManager::GetKeyState(DDC_Scancode::DDC_SC_2)) {

            _attachedGameObject->gameObjectRect.x += 5;
        }

        if (DDC_InputManager::GetKeyState(DDC_Scancode::DDC_SC_8)) {

            _attachedGameObject->gameObjectRect.y -= 5;
        }

        if (DDC_InputManager::GetKeyState(DDC_Scancode::DDC_SC_9)) {

            _attachedGameObject->gameObjectRect.y += 5;
        }

        if (DDC_InputManager::IsKeyDown() &&
            DDC_InputManager::GetKeyState(DDC_Scancode::DDC_SC_5)) {

            _attachedGameObject->gameObjectRect.x = 3093;
            _attachedGameObject->gameObjectRect.y = 51;
        }
    }

public:
    float playerGroundY;
    int life = 20;

    virtual void OnStart() {
        _worldCamera = Dodoco::GetDodoco()->GetCurrentScene()->GetWorldCamera();
        _sprite2d = _attachedGameObject
                        ->GetComponentByID<DDC_Sprite2D>(DDC_COMPONENT_SPRITE2D);
        DDC_AnimateSpriteRenderer::SetState(_attachedGameObject, _CONTRA_BILL_STATE_STAND);
        DDC_AnimateSpriteRenderer::ReplayGameObject(_attachedGameObject);
    }

    virtual void Update() {
        float &playerX = _attachedGameObject->gameObjectRect.x;
        float &playerY = _attachedGameObject->gameObjectRect.y;

        std::stringstream hpStr;
        hpStr << "LANCE  " << std::setw(2) << std::setfill('0') << life;
        Contra_RenderText(278, 1, 40, 6, hpStr.str());

        if (DDC_InputManager::IsKeyDown() &&
            DDC_InputManager::GetKeyState(DDC_Scancode::DDC_SC_0)) {

            _stop = !_stop;
        }

        _HandleGodKey();

        if (playerX > _worldCamera->gameObjectRect.x + 150 && playerX < 3155) {
            _worldCamera->gameObjectRect.x += playerX - _worldCamera->gameObjectRect.x - 150;
        }

        if (_stop)
            return;

performDeath:
        if (_inDeath) {
            _PerformDeath();
            return;
        }

        int groundY = CheckTouchGround();

        _shoting = false;

        if (groundY == -2 || _CheckHit()) {
            _inDeath = true;
            DDC_AnimateSpriteRenderer::SetState(_attachedGameObject,
                                                _CONTRA_BILL_STATE_DIE);
            DDC_AnimateSpriteRenderer::ReplayGameObject(_attachedGameObject);
            _deathStartTick = DDC_Time::GetTotalTicks();

            life--;
            _Contra_SharedData::life = life;

            goto performDeath;
        }

        if (groundY > 0 && vy >= 0) {
            playerY = groundY;
            vy = 0;
            _jumping = false;
        } else {
            playerY += vy * DDC_Time::GetTimeDeltaSecond();
            vy += g * DDC_Time::GetTimeDeltaSecond() * 10;
        }

        if (!_jumping)
            playerGroundY = playerY;

        _state = Contra_MobState::CONTRA_BILL_STAND;
        _directionVert = Contra_MobDirectionVert::CONTRA_DIR_VERT_NONE;

        if (DDC_InputManager::GetKeyState(DDC_Scancode::DDC_SC_W)) {
            /* w key only... */
            _directionVert = Contra_MobDirectionVert::CONTRA_DIR_UP;
        } else if (DDC_InputManager::GetKeyState(DDC_Scancode::DDC_SC_S)) {
            _directionVert = Contra_MobDirectionVert::CONTRA_DIR_DOWN;
        }

        if (DDC_InputManager::GetKeyState(DDC_Scancode::DDC_SC_D)) {
            _sprite2d->SetSpriteFlipMode(DDC_SpriteFlip::FLIP_NONE);

            if (DDC_InputManager::GetKeyState(DDC_Scancode::DDC_SC_W))
                _directionVert = Contra_MobDirectionVert::CONTRA_DIR_UP;

            _directionHorz = Contra_MobDirectionHoriz::CONTRA_DIR_RIGHT;
            _state = Contra_MobState::CONTRA_BILL_RUN;

            playerX += vx * DDC_Time::GetTimeDeltaSecond();
        } else if (DDC_InputManager::GetKeyState(DDC_Scancode::DDC_SC_A)) {
            _sprite2d->SetSpriteFlipMode(DDC_SpriteFlip::FLIP_HORIZONTAL);

            _directionHorz = Contra_MobDirectionHoriz::CONTRA_DIR_LEFT;
            _state = Contra_MobState::CONTRA_BILL_RUN;

            playerX -= vx * DDC_Time::GetTimeDeltaSecond();
        }

        if (DDC_InputManager::IsKeyDown() &&
            (DDC_InputManager::GetKeyState(DDC_Scancode::DDC_SC_SPACE) ||
             DDC_InputManager::GetKeyState(DDC_Scancode::DDC_SC_K)) && !_jumping) {

            DDC_AnimateSpriteRenderer::SetState(_attachedGameObject,
                                                _CONTRA_BILL_STATE_ROLL);

            _jumping = true;
            vy = -250;
            playerY -= 5;
        }

        if (_state == Contra_MobState::CONTRA_BILL_STAND &&
            _directionVert == Contra_MobDirectionVert::CONTRA_DIR_DOWN) {

            _state = Contra_MobState::CONTRA_BILL_FALL_FLAT;
            _directionVert = Contra_MobDirectionVert::CONTRA_DIR_VERT_NONE;
        }

        if (DDC_InputManager::IsKeyDown() &&
            DDC_InputManager::GetKeyState(DDC_Scancode::DDC_SC_J) &&
            DDC_Time::GetTotalTicks() - _lastShotTicks >= 100) {

            _lastShotTicks = DDC_Time::GetTotalTicks();

            int bx, by;
            DDC_Vector2 bulletV;

            _shoting = true;
            _shotStartTicks = DDC_Time::GetTotalTicks();

            _CalcBulletStartState(&bx, &by, &bulletV);
            Contra_BulletManager::ShotBullet(bx, by,
                                             bulletV,
                                             Contra_BulletType::CONTRA_BULLET_BILL);

            Mix_PlayChannel(-1, Contra_gMusicChucks[CONTRA_RES_AUDIO_FIRE], 0);
        }

        SetAnimateState();
    }
};

class Contra_EnemyManager: public DDC_Behavior {
    DDC_GameObject *_player, *_worldCamera;
    BillController *_billController;

    int _lastUpdateTicks;
    int _lastCheckTicks;

    static std::vector<DDC_GameObject *> _Enemy1Pool;
    static std::vector<DDC_GameObject *> _Enemy2Pool;

    static DDC_GameObject *_GetFreeEnemy(std::vector<DDC_GameObject*> &where) {
        for (DDC_GameObject *go: where) {
            if (_Contra_CameraCheckOutSide(go))
                return go;
        }
        return nullptr;
    }

    int _GetEnemy1SpawnSideInfo() {
        int side, size = _LevelEnemy1SpawnMeta.size();
        int playerX = _player->gameObjectRect.x;

        for (int i = 0; i < size; i += 3) {
            if (playerX >= _LevelEnemy1SpawnMeta[i] && playerX <= _LevelEnemy1SpawnMeta[i + 1])
                return _LevelEnemy1SpawnMeta[i + 2];
        }
        return -1;
    }

    void _GetEnemySpawnXY(int *x, int *y, bool *skip) {
        int oSide = _GetEnemy1SpawnSideInfo();
        bool side;

        if (oSide < 0)
            *skip = true;

        if (oSide == 2)
            side = _randomEngine() % 2;  /* side = 0 (left) | 1 (right) */
        else
            side = bool(oSide);

        *x = _worldCamera->gameObjectRect.x + (side ? 380 : -60);
        *y = 20;
    }

    void _CheckAndDoBulletHit() {
        for (DDC_GameObject *bullet: Contra_BulletManager::GetBillBulletPool()) {
            for (DDC_GameObject *enemy: _Enemy1Pool) {
                if (!_Contra_CameraCheckOutSide(bullet) &&
                    !_Contra_CameraCheckOutSide(enemy, 0, 40) &&
                    DDC_Math::RectIntersect(bullet->gameObjectRect, enemy->gameObjectRect)){

                    _Contra_MoveFree(bullet);
                    _Contra_MoveFree(enemy);

                    _score += 50;

                    Mix_PlayChannel(-1, Contra_gMusicChucks[CONTRA_RES_AUDIO_ENEMYDIE], 0);
                }
            }
            for (DDC_GameObject *enemy: _Enemy2Pool) {
                if (!_Contra_CameraCheckOutSide(bullet) &&
                    !_Contra_CameraCheckOutSide(enemy, 0, 40) &&
                    DDC_Math::RectIntersect(bullet->gameObjectRect, enemy->gameObjectRect)){

                    _Contra_MoveFree(bullet);
                    _Contra_MoveFree(enemy);

                    _score += 500;

                    Mix_PlayChannel(-1, Contra_gMusicChucks[CONTRA_RES_AUDIO_ENEMYDIE], 0);
                }
            }
        }
    }

public:
    static std::vector<DDC_GameObject *> GetEnemy1Pool() {
        return _Enemy1Pool;
    }

    static std::vector<DDC_GameObject *> GetEnemy2Pool() {
        return _Enemy2Pool;
    }

    static void InitEmeny1Pool(DDC_Scene *scene, DDC_BaseSDLTexture *texture) {
        DDC_GameObject *go;
        DDC_SpriteTexture *st;


        /* 20 enemies (type 1) in one screen at most. */
        for (int i = 0; i < 20; ++i) {
            DDC_SpriteAnimate *animEnemy1Run = new DDC_SpriteAnimate(_CONTRA_ANIM_PLAY_INTERVAL, 1);
            _LevelMemPool.push_back(animEnemy1Run);

            for (int i = 30; i <= 33; ++i) {
                st = DDC_SpriteTexture::CutFromBaseTexture(texture,
                                                           Contra_BillTextureCut[i]);
                animEnemy1Run->AddFrame(st);
                _LevelMemPool.push_back(st);
            }

            go = scene->NewGameObject("enemy1_");
            go->AddBehavior<Enemy1Controller>();
            DDC_AnimateSpriteRenderer *asr =
                go->AddCompoment<DDC_Sprite2D>()
                    ->UseSpriteRenderer<DDC_AnimateSpriteRenderer>();
            asr->AddAnimate(animEnemy1Run, &_CONTRA_ENEMY1_STATE_RUN);
            go->gameObjectRect = DDC_FRect {-100, -100, 18, 40};
            _Enemy1Pool.push_back(go);
        }
    }

    static void InitEnemy2Pool(DDC_Scene *scene, DDC_BaseSDLTexture *texture) {
        DDC_GameObject *go;
        DDC_SpriteTexture *st;

        DDC_SpriteAnimate *animEnemy2ShotR = new DDC_SpriteAnimate(_CONTRA_ANIM_PLAY_INTERVAL, 1);
        DDC_SpriteAnimate *animEnemy2ShotU = new DDC_SpriteAnimate(_CONTRA_ANIM_PLAY_INTERVAL, 1);

        st = DDC_SpriteTexture::CutFromBaseTexture(texture, Contra_BillTextureCut[35]);
        animEnemy2ShotR->AddFrame(st);
        _LevelMemPool.push_back(st);

        st = DDC_SpriteTexture::CutFromBaseTexture(texture, Contra_BillTextureCut[36]);
        animEnemy2ShotU->AddFrame(st);
        _LevelMemPool.push_back(st);

        for (int i = 0; i < _Enemy2SpawnPoints.size(); ++i) {
            go = scene->NewGameObject("enemy2_");
            go->AddBehavior<Enemy2Controller>();
            DDC_AnimateSpriteRenderer *asr =
                go->AddCompoment<DDC_Sprite2D>()
                    ->UseSpriteRenderer<DDC_AnimateSpriteRenderer>();
            asr->AddAnimate(animEnemy2ShotR, &_CONTRA_ENEMY2_STATE_SHOTR);
            asr->AddAnimate(animEnemy2ShotU, &_CONTRA_ENEMY2_STATE_SHOTU);
            go->gameObjectRect = DDC_FRect {_Enemy2SpawnPoints[i].x,
                                            _Enemy2SpawnPoints[i].y,
                                            24, 40};
            _Enemy2Pool.push_back(go);
        }
    }

    void SpawnEnemy1() {
        DDC_GameObject *enemy = _GetFreeEnemy(_Enemy1Pool);

        if (enemy == nullptr) {
            std::cerr << "[Contra] Warning: SpawnEnemy1() no more free enemy1.\n";
            return;
        }

        int x, y;
        bool skip;
        _GetEnemySpawnXY(&x, &y, &skip);

        if (skip)
            return;

        enemy->gameObjectRect.x = x;
        enemy->gameObjectRect.y = y;

        Enemy1Controller *controller = enemy->GetBehavior<Enemy1Controller>();
        controller->WalkTowardPlayer();
    }

    virtual void OnStart() {
        _player = _attachedGameObject->GetAttachedScene()->GetGameObejct("bill");
        if (_player == nullptr)
            std::cerr << "[Contra] Warning: enemy manager cannot get player pointer\n";
        _billController = _player->GetBehavior<BillController>();

        _worldCamera = _attachedGameObject->GetAttachedScene()->GetWorldCamera();
        _lastUpdateTicks = DDC_Time::GetTotalTicks();
    }

    virtual void Update() {
        // check bullet hit at 30Hz
        if (DDC_Time::GetTotalTicks() - _lastCheckTicks >= 33) {
            _lastCheckTicks = DDC_Time::GetTotalTicks();
            _CheckAndDoBulletHit();
        }
        if (DDC_Time::GetTotalTicks() - _lastUpdateTicks >= _randomEngine() % 1000 + 1200) {
            _lastUpdateTicks = DDC_Time::GetTotalTicks();
            SpawnEnemy1();
        }

        std::stringstream scoreStr;
        scoreStr << "SCORE   " << std::setw(6) << std::setfill('0') << _score;
        Contra_RenderText(2, 1, 60, 6, scoreStr.str());
    }
};

std::vector<DDC_GameObject *> Contra_EnemyManager::_Enemy1Pool = {};
std::vector<DDC_GameObject *> Contra_EnemyManager::_Enemy2Pool = {};

bool BillController::_CheckHit() {
    if (DDC_Time::GetTotalTicks() - _superTimeStartTicks <= 3000) {
        return false;
    }

    DDC_FRect playerCollider = _attachedGameObject->gameObjectRect;

    if (_state == Contra_MobState::CONTRA_BILL_FALL_FLAT) {
        playerCollider.x += 0;
        playerCollider.y += 25;
        playerCollider.height = _fallFlatColliderHeight;
        playerCollider.width = _fallFlatColliderWidth;
    } else {
        playerCollider.x += 5;
        playerCollider.y += 5;
        playerCollider.height -= 5;
        playerCollider.width -= 5;
    }
    if (DDC_Time::GetTotalTicks() - _lastDeathCheckTicks >= 33) {
        _lastDeathCheckTicks = DDC_Time::GetTotalTicks();

        for (DDC_GameObject *bullet: Contra_BulletManager::GetEnemyBulletPool()) {
            if (DDC_Math::RectIntersect(playerCollider, bullet->gameObjectRect))
                goto hit;
        }

        for (DDC_GameObject *bullet: Contra_BulletManager::GetBossBulletPool()) {
            if (DDC_Math::RectIntersect(playerCollider, bullet->gameObjectRect))
                goto hit;
        }

        for (DDC_GameObject *enemy1: Contra_EnemyManager::GetEnemy1Pool()) {
            if (DDC_Math::RectIntersect(playerCollider, enemy1->gameObjectRect))
                goto hit;
        }

        return false;

hit:
        _superTimeStartTicks = DDC_Time::GetTotalTicks();
        return true;
    }
}

class Contra_MainGameAudioPlayer: public DDC_Behavior {
    int _lastPlayTicks = -151000;
public:
    virtual void Update() {
        if (DDC_Time::GetTotalTicks() - _lastPlayTicks >= 151000) {
            _lastPlayTicks = DDC_Time::GetTotalTicks();
            Mix_Chunk *chunk = Contra_gMusicChucks[CONTRA_RES_AUDIO_STAGE1];
            Mix_PlayChannel(-1, chunk, 0);
        }
    }
};

void Contra_InitLevelGameObject(DDC_GameObject *level) {
    DDC_BaseSDLTexture *levelTexture = DDC_BaseSDLTexture::LoadFromFile("res/texture/level.png");
    _LevelMemPool.push_back((void*) levelTexture);

    DDC_SpriteTexture *st =
        DDC_SpriteTexture::CutFromBaseTexture(levelTexture, DDC_Rect{0, 0, 3328, 240});
    _LevelMemPool.push_back((void*) st);

    st->SetGameObjectSizeAsTextureSize(level);

    level->AddCompoment<DDC_Sprite2D>()
         ->UseSpriteRenderer<DDC_StaticSpriteRenderer>()
         ->SetSpriteTexture(st);
}

void Contra_InitBillObject(DDC_GameObject *bill, DDC_BaseSDLTexture *texture) {
    bill->gameObjectRect.x = 46;
    bill->gameObjectRect.y = 30;
    bill->gameObjectRect.height = 40;
    bill->gameObjectRect.width = 25;

    DDC_SpriteTexture *texStand =
        DDC_SpriteTexture::CutFromBaseTexture(texture, Contra_BillTextureCut[0]);
    _LevelMemPool.push_back(texStand);

    DDC_SpriteTexture *texFallFlat =
        DDC_SpriteTexture::CutFromBaseTexture(texture, Contra_BillTextureCut[2]);
    _LevelMemPool.push_back(texFallFlat);

    DDC_SpriteTexture *texStandDownRight =
        DDC_SpriteTexture::CutFromBaseTexture(texture, Contra_BillTextureCut[4]);
    _LevelMemPool.push_back(texStandDownRight);

    DDC_SpriteTexture *texStandUpRight =
        DDC_SpriteTexture::CutFromBaseTexture(texture, Contra_BillTextureCut[3]);
    _LevelMemPool.push_back(texStandUpRight);

    DDC_SpriteTexture *texStandUp =
        DDC_SpriteTexture::CutFromBaseTexture(texture, Contra_BillTextureCut[1]);
    _LevelMemPool.push_back(texStandUp);

    DDC_SpriteAnimate *animWalk = new DDC_SpriteAnimate(_CONTRA_ANIM_PLAY_INTERVAL, 1);
    DDC_SpriteAnimate *animWalkShotR = new DDC_SpriteAnimate(_CONTRA_ANIM_PLAY_INTERVAL, 1);
    DDC_SpriteAnimate *animWalkShotUR = new DDC_SpriteAnimate(_CONTRA_ANIM_PLAY_INTERVAL, 1);
    DDC_SpriteAnimate *animWalkShotDR = new DDC_SpriteAnimate(_CONTRA_ANIM_PLAY_INTERVAL, 1);
    DDC_SpriteAnimate *animStand = new DDC_SpriteAnimate(_CONTRA_ANIM_PLAY_INTERVAL, 1);
    DDC_SpriteAnimate *animFallFlat = new DDC_SpriteAnimate(_CONTRA_ANIM_PLAY_INTERVAL, 1);
    DDC_SpriteAnimate *animStandUR = new DDC_SpriteAnimate(_CONTRA_ANIM_PLAY_INTERVAL, 1);
    DDC_SpriteAnimate *animStandDR = new DDC_SpriteAnimate(_CONTRA_ANIM_PLAY_INTERVAL, 1);
    DDC_SpriteAnimate *animStandUP = new DDC_SpriteAnimate(_CONTRA_ANIM_PLAY_INTERVAL, 1);
    DDC_SpriteAnimate *animRoll = new DDC_SpriteAnimate(_CONTRA_ANIM_PLAY_INTERVAL, 1);
    DDC_SpriteAnimate *animDie = new DDC_SpriteAnimate(_CONTRA_ANIM_PLAY_INTERVAL, 0);

    _LevelMemPool.push_back(animWalk);
    _LevelMemPool.push_back(animWalkShotR);
    _LevelMemPool.push_back(animWalkShotUR);
    _LevelMemPool.push_back(animWalkShotDR);
    _LevelMemPool.push_back(animStand);
    _LevelMemPool.push_back(animFallFlat);
    _LevelMemPool.push_back(animStandUR);
    _LevelMemPool.push_back(animStandDR);
    _LevelMemPool.push_back(animStandUP);
    _LevelMemPool.push_back(animRoll);
    _LevelMemPool.push_back(animDie);

    DDC_SpriteTexture *st;

    st = DDC_SpriteTexture::CutFromBaseTexture(texture, Contra_BillTextureCut[0]);
    animStand->AddFrame(st);
    _LevelMemPool.push_back(st);

    st = DDC_SpriteTexture::CutFromBaseTexture(texture, Contra_BillTextureCut[2]);
    animFallFlat->AddFrame(st);
    _LevelMemPool.push_back(st);

    st = DDC_SpriteTexture::CutFromBaseTexture(texture, Contra_BillTextureCut[3]);
    animStandUR->AddFrame(st);
    _LevelMemPool.push_back(st);

    st = DDC_SpriteTexture::CutFromBaseTexture(texture, Contra_BillTextureCut[4]);
    animStandDR->AddFrame(st);
    _LevelMemPool.push_back(st);

    st = DDC_SpriteTexture::CutFromBaseTexture(texture, Contra_BillTextureCut[1]);
    animStandUP->AddFrame(st);
    _LevelMemPool.push_back(st);

    for (int i = 5; i <= 11; ++i) {
        st = DDC_SpriteTexture::CutFromBaseTexture(texture,
                                                   Contra_BillTextureCut[i]);
        animWalk->AddFrame(st);
        _LevelMemPool.push_back(st);
    }

    for (int i = 12; i <= 14; ++i) {
        st = DDC_SpriteTexture::CutFromBaseTexture(texture,
                                                   Contra_BillTextureCut[i]);
        animWalkShotR->AddFrame(st);
        _LevelMemPool.push_back(st);
    }

    for (int i = 15; i <= 17; ++i) {
        st = DDC_SpriteTexture::CutFromBaseTexture(texture,
                                                   Contra_BillTextureCut[i]);
        animWalkShotUR->AddFrame(st);
        _LevelMemPool.push_back(st);
    }

    for (int i = 18; i <= 20; ++i) {
        st = DDC_SpriteTexture::CutFromBaseTexture(texture,
                                                   Contra_BillTextureCut[i]);
        animWalkShotDR->AddFrame(st);
        _LevelMemPool.push_back(st);
    }

    for (int i = 21; i <= 24; ++i) {
        st = DDC_SpriteTexture::CutFromBaseTexture(texture,
                                                   Contra_BillTextureCut[i]);
        animRoll->AddFrame(st);
        _LevelMemPool.push_back(st);
    }

    for (int i = 25; i <= 29; ++i) {
        st = DDC_SpriteTexture::CutFromBaseTexture(texture,
                                                   Contra_BillTextureCut[i]);
        animDie->AddFrame(st);
        _LevelMemPool.push_back(st);
    }

    DDC_Sprite2D *s2d = bill->AddCompoment<DDC_Sprite2D>();
    DDC_AnimateSpriteRenderer *asr = s2d->UseSpriteRenderer<DDC_AnimateSpriteRenderer>();
    asr->AddAnimate(animWalk, &_CONTRA_BILL_STATE_WALKR);
    asr->AddAnimate(animWalkShotR, &_CONTRA_BILL_STATE_WALKS);
    asr->AddAnimate(animWalkShotDR, &_CONTRA_BILL_STATE_WALKSDR);
    asr->AddAnimate(animWalkShotUR, &_CONTRA_BILL_STATE_WALKSUR);
    asr->AddAnimate(animStand, &_CONTRA_BILL_STATE_STAND);
    asr->AddAnimate(animFallFlat, &_CONTRA_BILL_STATE_FALLFLAT);
    asr->AddAnimate(animStandUP, &_CONTRA_BILL_STATE_STANDUP);
    asr->AddAnimate(animStandUR, &_CONTRA_BILL_STATE_STANDUR);
    asr->AddAnimate(animStandDR, &_CONTRA_BILL_STATE_STANDDR);
    asr->AddAnimate(animRoll, &_CONTRA_BILL_STATE_ROLL);
    asr->AddAnimate(animDie, &_CONTRA_BILL_STATE_DIE);

    bill->AddBehavior<BillController>();
    s2d->keepTextureScale = true;
}

DDC_Scene *Contra_MainGameSceneCreate() {
    DDC_Scene *scene = Dodoco::GetDodoco()->NewScene("MainGame");

    DDC_BaseSDLTexture *charactorTexture =
        DDC_BaseSDLTexture::LoadFromFile("res/texture/texture.png");

    DDC_GameObject *worldCamera = scene->GetWorldCamera();

    scene->screenLogicalSizeWidth = 320;
    scene->screenLogicalSizeHeight = 240;

    DDC_SpriteTexture *billBulletTexture = DDC_SpriteTexture::CutFromBaseTexture(
                                                charactorTexture,
                                                DDC_Rect {89, 7, 5, 5});

    DDC_SpriteTexture *enemyBulletTexture = DDC_SpriteTexture::CutFromBaseTexture(
                                                charactorTexture,
                                                DDC_Rect {82, 8, 3, 3});

    DDC_SpriteTexture *bossBulletTexture = DDC_SpriteTexture::CutFromBaseTexture(
                                                charactorTexture,
                                                DDC_Rect {108, 6, 8, 8});

    _LevelMemPool.push_back(billBulletTexture);
    _LevelMemPool.push_back(enemyBulletTexture);
    _LevelMemPool.push_back(bossBulletTexture);

    DDC_GameObject *go = scene->NewGameObject("level");
    Contra_InitLevelGameObject(go);

    DDC_GameObject *bill = scene->NewGameObject("bill");
    Contra_InitBillObject(bill, charactorTexture);

    Contra_BulletManager::InitBillBulletPool(billBulletTexture, scene);
    Contra_BulletManager::InitEnemyBulletPool(enemyBulletTexture, scene);
    Contra_BulletManager::InitBossBulletPool(bossBulletTexture, scene);

    Contra_EnemyManager::InitEmeny1Pool(scene, charactorTexture);
    Contra_EnemyManager::InitEnemy2Pool(scene, charactorTexture);

    DDC_GameObject *enemySpawmer = scene->NewGameObject("EnemySpawmer");
    enemySpawmer->AddBehavior<Contra_EnemyManager>();

    DDC_GameObject *boss = scene->NewGameObject("Boss");
    boss->AddBehavior<BossController>();
    boss->gameObjectRect = DDC_FRect{3228, 169, 11, 14};

    DDC_GameObject *audiotrack = scene->NewGameObject("audioPlayer");
    audiotrack->AddBehavior<Contra_MainGameAudioPlayer>();

    return scene;
}

void Contra_MainGameSceneDestory() {}

// } Main Game Scene

// Fail Settlement Scene {

std::vector<void *> _failSceneMemPool = {};

class TextController: public DDC_Behavior {
    int _pointValue;

    void _DoTextInceraseAnimate() {
        std::stringstream pointStr;
        pointStr << "SCORE = " << std::setw(6) << std::setfill('0') << _pointValue;
        if (_pointValue < _Contra_SharedData::score)
            _pointValue += 25;
        else
            _pointValue = _Contra_SharedData::score;
        Contra_RenderText(83, 110, 135, 22, pointStr.str());
    }
public:
    virtual void Update() {
        Contra_RenderText(56, 58, 212, 31, "GAME OVER");
        _DoTextInceraseAnimate();
    }
};

DDC_Scene *Contra_FailSettlementSceneCreate() {
    DDC_BaseSDLTexture *bgTexture = DDC_BaseSDLTexture::LoadFromFile("res/texture/finalbg.png");
    _failSceneMemPool.push_back(bgTexture);

    DDC_SpriteTexture *bgSpriteTexture =
            DDC_SpriteTexture::CutFromBaseTexture(bgTexture, DDC_Rect {0, 0, 320, 240});
    _failSceneMemPool.push_back(bgSpriteTexture);

    DDC_Scene *scene = Dodoco::GetDodoco()->NewScene("FailScene");

    DDC_GameObject *bg = scene->NewGameObject("background");
    bg->gameObjectRect = DDC_FRect {0, 0, 320, 240};

    DDC_GameObject *text = scene->NewGameObject("text");
    text->AddBehavior<TextController>();

    DDC_StaticSpriteRenderer *ssr =
        bg->AddCompoment<DDC_Sprite2D>()->UseSpriteRenderer<DDC_StaticSpriteRenderer>();
    ssr->SetSpriteTexture(bgSpriteTexture);

    scene->screenLogicalSizeWidth = 320;
    scene->screenLogicalSizeHeight = 240;

    return scene;
}

void Contra_FailSettlementSceneDestory() {}

// } Fail Settlement Scene

// Win Settlement Scene {

std::vector<void *> _winSceneMemPool = {};

class WinTextController: public DDC_Behavior {
    int _pointValue;

    bool _showTotal = false;

    void _DoTextInceraseAnimate() {
        int target = _Contra_SharedData::score + _Contra_SharedData::life * 1000;

        std::stringstream titleStr, pointStr;
        titleStr << "SCORE + " << _Contra_SharedData::life << " x 1000 = ";
        pointStr << std::setw(6) << std::setfill('0') << _pointValue;

        Contra_RenderText(59, 96, 209, 22, titleStr.str());
        Contra_RenderText(107, 138, 113, 22, pointStr.str());

        if (_pointValue < target)
            _pointValue += 25;
        else {
            _pointValue = target;
            _showTotal = true;
        }
    }
public:
    virtual void Update() {
        Contra_RenderText(56, 58, 212, 31, "YOU WIN");
        _DoTextInceraseAnimate();
    }
};

DDC_Scene *Contra_WinSettlementSceneCreate() {
    DDC_Scene *scene = Dodoco::GetDodoco()->NewScene("WinScene");

    DDC_BaseSDLTexture *bgTexture = DDC_BaseSDLTexture::LoadFromFile("res/texture/finalbg.png");
    _winSceneMemPool.push_back(bgTexture);

    DDC_SpriteTexture *bgSpriteTexture =
            DDC_SpriteTexture::CutFromBaseTexture(bgTexture, DDC_Rect {0, 0, 320, 240});
    _winSceneMemPool.push_back(bgSpriteTexture);

    DDC_GameObject *bg = scene->NewGameObject("background");
    bg->gameObjectRect = DDC_FRect {0, 0, 320, 240};

    DDC_GameObject *text = scene->NewGameObject("text");

    text->AddBehavior<WinTextController>();

    DDC_StaticSpriteRenderer *ssr =
        bg->AddCompoment<DDC_Sprite2D>()->UseSpriteRenderer<DDC_StaticSpriteRenderer>();
    ssr->SetSpriteTexture(bgSpriteTexture);

    scene->screenLogicalSizeWidth = 320;
    scene->screenLogicalSizeHeight = 240;

    return scene;
}

void Contra_WinSettlementSceneDestory() {}

// } Win Settlement Scene
